home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume8 / hier < prev    next >
Encoding:
Internet Message Format  |  1987-02-11  |  29.8 KB

  1. Subject:  v08i057:  Directory hiearchy scanner
  2. Newsgroups: mod.sources
  3. Approved: mirror!rs
  4.  
  5. Submitted by: Alan Silverstein <seismo!hplabs!hpfcla!hpfcdt!ajs>
  6. Mod.sources: Volume 8, Issue 57
  7. Archive-name: hier
  8.  
  9. hier(1) is yet another way to view a directory hierarchy.  It's
  10. analogous to ls -R, but presents the data in a new fashion especially
  11. useful for novice users or display on a wall.  Now, with the plethora of
  12. similar tools available, what's special about this one?
  13.  
  14. - I wrote it after four *years* of consideration and using similar tools.
  15. - I interacted with an "end user" who knew what he wanted.
  16. - The code is carefully crafted, commented, and tested.  (But not yet
  17.   widely ported, I fear.)
  18.  
  19. So give it a try, and see if you like it.
  20.  
  21. Also note!  This package includes a "sorted ftw()", which is a major
  22. and non-trivial library routine you'll love if you are familiar with
  23. ftw(3) (file tree walk), but wish you could get sorted results.
  24.  
  25. Alan Silverstein, Hewlett-Packard Systems Software Operation, Fort Collins,
  26. Colorado; {ihnp4 | hplabs}!hpfcla!ajs; 303-229-3053; (lat-long on request :-)
  27.  
  28. [  I ported SFTW to BSD.  This included writing (most of?) <ftw.h>
  29.    and a couple of real minor tweaks.  I started on HIER, but gave up
  30.    at the first bug.  A public-domain manpage for sftw would be real
  31.    nice.  -r$ ]
  32.  
  33. #! /bin/sh
  34. # This is a shell archive.  Remove anything before this line,
  35. # then unpack it by saving it in a file and typing "sh file".
  36. # If all goes well, you will see the message "End of shell archive."
  37. # Contents:  Makefile Makefile.bsd ftw.h hier.1 hier.c sftw.c
  38. PATH=/bin:/usr/bin:/usr/ucb; export PATH
  39. echo shar: extracting "'Makefile'" '(324 characters)'
  40. if test -f 'Makefile' ; then 
  41.   echo shar: will not over-write existing file "'Makefile'"
  42. else
  43. sed 's/^X//' >Makefile <<'@//E*O*F Makefile//'
  44. X# Makefile for hier(1), needed to link with sftw().
  45. X
  46. Xhier: hier.c sftw.o
  47. X    cc -v -O -s -o hier hier.c sftw.o
  48. X
  49. Xsftw.o: sftw.c
  50. X    cc -v -O -c sftw.c
  51. X
  52. Xdebug: hier.c sftw.c
  53. X    cc -v -g -o hier hier.c sftw.c
  54. X    rm -f hier.o sftw.o    # since they're useless.
  55. X
  56. Xtest: sftw.c
  57. X    cc -v -O -o sftw sftw.c -DTEST
  58. X
  59. Xclean:
  60. X    rm -f *.o hier sftw core
  61. @//E*O*F Makefile//
  62. if test 324 -ne "`wc -c <'Makefile'`"; then
  63.     echo shar: error transmitting "'Makefile'" '(should have been 324 characters)'
  64. fi
  65. fi # end of overwriting check
  66. echo shar: extracting "'Makefile.bsd'" '(298 characters)'
  67. if test -f 'Makefile.bsd' ; then 
  68.   echo shar: will not over-write existing file "'Makefile.bsd'"
  69. else
  70. sed 's/^X//' >Makefile.bsd <<'@//E*O*F Makefile.bsd//'
  71. X# Makefile for hier(1), needed to link with sftw().
  72. X# This is for BSD sites.  mirror!rs
  73. X
  74. XCFLAGS = -O -DBSD -I. -Dstrrchr=rindex
  75. XGETOPT=-lgetopt
  76. X
  77. Xhier: hier.c sftw.o
  78. X    cc $(CFLAGS) -s -o hier hier.c sftw.o $(GETOPT)
  79. X
  80. Xtest: sftw.c
  81. X    cc $(CFLAGS) -DTEST -o sftw sftw.c
  82. X
  83. Xclean:
  84. X    rm -f *.o hier sftw core
  85. @//E*O*F Makefile.bsd//
  86. if test 298 -ne "`wc -c <'Makefile.bsd'`"; then
  87.     echo shar: error transmitting "'Makefile.bsd'" '(should have been 298 characters)'
  88. fi
  89. fi # end of overwriting check
  90. echo shar: extracting "'ftw.h'" '(576 characters)'
  91. if test -f 'ftw.h' ; then 
  92.   echo shar: will not over-write existing file "'ftw.h'"
  93. else
  94. sed 's/^X//' >ftw.h <<'@//E*O*F ftw.h//'
  95. X/*
  96. X**  This is a version of <ftw.h> for sites that don't have it.  I just
  97. X**  picked four values at random for the #define's that sftw needed; I
  98. X**  doubt that this is binary-compatible with AT&T's <ftw.h>, and this
  99. X**  may well be missing something.  Oh, well, it's public-domain...
  100. X**    -Rich $alz, mirror!rs
  101. X*/
  102. X
  103. X
  104. X/*
  105. X**  These values are passed on to the user's function, as the third
  106. X**  parameter.
  107. X*/
  108. X#define    FTW_F        1    /* A file                */
  109. X#define    FTW_D        2    /* A directory                */
  110. X#define    FTW_DNR        3    /* A directory that couldn't be read    */
  111. X#define    FTW_NS        4    /* A stat() failure            */
  112. @//E*O*F ftw.h//
  113. if test 576 -ne "`wc -c <'ftw.h'`"; then
  114.     echo shar: error transmitting "'ftw.h'" '(should have been 576 characters)'
  115. fi
  116. fi # end of overwriting check
  117. echo shar: extracting "'hier.1'" '(1732 characters)'
  118. if test -f 'hier.1' ; then 
  119.   echo shar: will not over-write existing file "'hier.1'"
  120. else
  121. sed 's/^X//' >hier.1 <<'@//E*O*F hier.1//'
  122. X.TH HIER 1 "Unsupported Utility"
  123. X.SH NAME
  124. Xhier \- show filesystem hierarchy
  125. X.SH SYNOPSIS
  126. X.B hier
  127. X[
  128. X.B \-adp
  129. X] [
  130. X.BI \-c \0columns
  131. X] [
  132. X.BI \-i \0indent
  133. X]
  134. X.br
  135. X[
  136. X.I directories...
  137. X]
  138. X.ad b
  139. X.SH DESCRIPTION
  140. XThis command shows you a filesystem hierarchy in a useful, indented way.
  141. XAt each level files are sorted in two groups:
  142. Xnon-directory files,
  143. Xthen directories (recursing into each one).
  144. XIt examines the named
  145. X.IR directories ,
  146. Xor by default the present working directory.
  147. X.PP
  148. XOptions are:
  149. X.TP
  150. X.B \-a
  151. XAll: include directories and files whose names start with ".".
  152. X.TP
  153. X.B \-d
  154. XShow directories only; skip other types of files.
  155. X.TP
  156. X.B \-p
  157. XPrint filenames packed onto lines, not aligned in columns.
  158. X.TP
  159. X.B \-c
  160. XSet width of display for showing multiple filenames on a line
  161. X(or use the COLUMNS environment variable).
  162. XThe default is 80 columns.
  163. X.TP
  164. X.B \-i
  165. XSet indentation (number of blanks) per hierarchy level.
  166. XThe default is 4 spaces per level.
  167. X.SH EXAMPLES
  168. X.TP
  169. Xhier
  170. X.br
  171. XShow all non-"." files, recursively,
  172. Xin and under the current directory.
  173. X.TP
  174. Xhier -apc 40 /etc
  175. XShow all directories and files,
  176. Xincluding any whose filenames start with ".",
  177. Xin a format 40 columns wide,
  178. Xand with filenames packed into lines,
  179. Xunder directory "/etc".
  180. X.SH SEE ALSO
  181. Xls(1), sftw(3) (does not exist (yet))
  182. X.SH DIAGNOSTICS
  183. XIf a file is not stat-able,
  184. Xor a directory is not readable,
  185. Xthe filename is printed on a line to itself,
  186. Xlike a directory (sorted with directory names),
  187. Xwith an appropriate message following.
  188. X.SH BUGS
  189. XUnlike
  190. X.IR ls (1),
  191. Xit sorts files across lines rather than down columns.
  192. XFixing this would be non-trivial.
  193. X.PP
  194. XAlso, due to the behavior of
  195. X.IR sftw (3)
  196. X(like
  197. X.IR ftw (3)),
  198. Xit never lists "." and ".." files, even with the
  199. X.B \-a
  200. Xoption.
  201. @//E*O*F hier.1//
  202. if test 1732 -ne "`wc -c <'hier.1'`"; then
  203.     echo shar: error transmitting "'hier.1'" '(should have been 1732 characters)'
  204. fi
  205. fi # end of overwriting check
  206. echo shar: extracting "'hier.c'" '(8409 characters)'
  207. if test -f 'hier.c' ; then 
  208.   echo shar: will not over-write existing file "'hier.c'"
  209. else
  210. sed 's/^X//' >hier.c <<'@//E*O*F hier.c//'
  211. X/*
  212. X * Show file system hierarchy.
  213. X *
  214. X * Usage: see below.
  215. X *
  216. X * Unlike ls(1), it sorts files across lines rather than down columns.
  217. X * Fixing this would be non-trivial, involving saving filenames until it
  218. X * was time to dump them.
  219. X *
  220. X * Also, due to the behavior of sftw() (like ftw()), it never lists "." and
  221. X * ".." files.
  222. X *
  223. X * Warning:  If you use ftw() instead of sftw(), -a option will stop working.
  224. X *
  225. X * Warning:  If you use ftw() instead of sftw(), a bug will appear.  This is
  226. X * because two calls in a row from ftw() to OneFile() may be made for ordinary
  227. X * files, where the second is in a directory a level above the first one.
  228. X * OneFile() would have to check to see if each ordinary file's path is
  229. X * different than the previous one's, indicating a change of directory level.
  230. X */
  231. X
  232. X#include <sys/types.h>
  233. X#include <sys/stat.h>
  234. X#include <stdio.h>
  235. X#include <ftw.h>
  236. X#ifdef    BSD
  237. X#include <sys/dir.h>            /* for DIRSIZ */
  238. X#include <strings.h>
  239. X#else
  240. X#include <sys/ndir.h>            /* for DIRSIZ */
  241. X#include <string.h>
  242. X#endif    /* BSD */
  243. X
  244. X
  245. X/*********************************************************************
  246. X * MISCELLANEOUS GLOBAL VALUES:
  247. X */
  248. X
  249. X#define    PROC                /* null; easy to find procs */
  250. X#define    FALSE    0
  251. X#define    TRUE    1
  252. X#define    CHNULL    ('\0')
  253. X#define    CPNULL    ((char *) NULL)
  254. X#define    REG    register
  255. X
  256. Xchar *usage[] = {
  257. X    "usage: %s [-adp] [-c columns] [-i indent] [directories...]",
  258. X    "-a include directories and files whose names start with \".\"",
  259. X    "-d show directories only",
  260. X    "-p print filenames packed onto lines, not aligned",
  261. X    "-c set width of display (or use COLUMNS env variable; default = 80)",
  262. X    "-i set indentation per level; default = 4",
  263. X    "Does current directory by default.",
  264. X    CPNULL,
  265. X};
  266. X
  267. Xchar    *myname;            /* how program was invoked    */
  268. Xint    aflag    = FALSE;        /* -a (all files) option    */
  269. Xint    dflag    = FALSE;        /* -d (directories) option    */
  270. Xint    pflag    = FALSE;        /* -p (packed filenames) option    */
  271. Xint    columns    = 0;            /* from -c option or env    */
  272. Xint    indent    = 0;            /* from -i option or default    */
  273. X
  274. X#define    COLUMNS    80            /* width of display        */
  275. X#define    INDENT     4            /* per directory level        */
  276. X
  277. Xint    startlen;            /* of current arg (filename)    */
  278. Xint    nextcol = 0;            /* column in output line    */
  279. X
  280. X
  281. X/************************************************************************
  282. X * M A I N
  283. X *
  284. X * Initialize, then call sftw() for each given filename after clearing
  285. X * global startlen to indicate a new starting file.  When done, if global
  286. X * nextcol != 0 (in the middle of an output line), finish the last line.
  287. X */
  288. X
  289. XPROC main (argc, argv)
  290. X    int    argc;
  291. X    char    **argv;
  292. X{
  293. Xextern    int    optind;            /* from getopt()    */
  294. Xextern    char    *optarg;        /* from getopt()    */
  295. XREG    int    option;            /* option "letter"    */
  296. X    char    *argdef = ".";        /* default argument    */
  297. X    char    *colstr;        /* from environment    */
  298. X
  299. X    char    *getenv();
  300. X    int    OneFile();
  301. X
  302. X/* #define DEPTH (_NFILE - 5)        /* for ftw(), but not sftw() */
  303. X
  304. X/*
  305. X * PARSE ARGUMENTS:
  306. X */
  307. X
  308. X    myname = *argv;
  309. X
  310. X    while ((option = getopt (argc, argv, "adpc:i:")) != EOF)
  311. X    {
  312. X        switch (option)
  313. X        {
  314. X        case 'a':    aflag    = TRUE;            break;
  315. X        case 'd':    dflag    = TRUE;            break;
  316. X        case 'p':    pflag    = TRUE;            break;
  317. X        case 'c':    columns    = atoi (optarg);    break;
  318. X        case 'i':    indent    = atoi (optarg);    break;
  319. X        default:    Usage();
  320. X        }
  321. X    }
  322. X
  323. X    if (dflag && pflag)
  324. X        Error ("-d and -p don't make sense together");
  325. X
  326. X/*
  327. X * FINISH INITIALIZING:
  328. X */
  329. X
  330. X    if ((columns == 0)                /* no value given */
  331. X     && (((colstr = getenv ("COLUMNS")) == CPNULL)    /* undefined      */
  332. X      || ((columns = atoi (colstr)) == 0)))      /* defined null or zero */
  333. X    {
  334. X        columns = COLUMNS;        /* use default */
  335. X    }
  336. X
  337. X    if (indent == 0)        /* no value given */
  338. X        indent = INDENT;        /* use default      */
  339. X
  340. X    argc -= optind;            /* skip options    */
  341. X    argv += optind;
  342. X
  343. X    if (argc == 0)            /* no filenames given */
  344. X    {
  345. X        argc = 1;
  346. X        argv = & argdef;        /* use default */
  347. X    }
  348. X
  349. X/*
  350. X * WALK EACH FILE TREE:
  351. X */
  352. X
  353. X    while (argc-- > 0)
  354. X    {
  355. X        startlen = 0;
  356. X
  357. X        if (sftw (*argv, OneFile, aflag))
  358. X        Error ("file tree walk failed for file \"%s\"", *argv);
  359. X
  360. X        argv++;
  361. X    }
  362. X
  363. X    if (nextcol)            /* last line not finished */
  364. X        putchar ('\n');
  365. X
  366. X    exit (0);
  367. X
  368. X} /* main */
  369. X
  370. X
  371. X/************************************************************************
  372. X * O N E   F I L E
  373. X *
  374. X * Called from sftw() to handle (print) one file, given a filename (starting
  375. X * file plus sub-file part) and ftw() file type.  Always returns zero (all
  376. X * is well, keep going).
  377. X *
  378. X * It's messy because of the need to print multiple non-directory basenames
  379. X * on one line.  Uses global startlen to save time figuring depth beyond
  380. X * starting file.  If currently zero, this is the starting file; print the
  381. X * fullname, on a line alone, with no indent.
  382. X *
  383. X * Use globals startlen, indent, columns, and nextcol.
  384. X */
  385. X
  386. XPROC int OneFile (filename, statp, type)
  387. X    char    *filename;    /* name        */
  388. X    struct    stat *statp;    /* info, unused    */
  389. X    int    type;        /* ftw() type    */
  390. X{
  391. XREG    char    *basename;    /* part of filename */
  392. X
  393. X/*
  394. X * PRINTING FORMATS (matching ftw() types):
  395. X */
  396. X
  397. Xstatic    char    *FMT_D   = "%s/\n";
  398. Xstatic    char    *FMT_DNR = "%s/ (not readable)\n";
  399. Xstatic    char    *FMT_NS  = "%s (not stat'able)\n";
  400. Xstatic    char    *FMT_F1     = "%s\n";        /* for starting file */
  401. Xstatic    char    *FMT_F2     = "%-*s";        /* for sub-file         */
  402. X
  403. X#ifdef    BSD
  404. X#define    FILEWIDTH MAXNAMLEN            /* for FMT_F2 */
  405. X#else
  406. X#define    FILEWIDTH (DIRSIZ + 1)            /* for FMT_F2 */
  407. X#endif    /* BSD */
  408. XREG    int    filewidth = FILEWIDTH;        /* if ! pflag */
  409. X
  410. X#define    NEWLINE     { putchar ('\n'); nextcol = 0; }  /* for speed and clarity */
  411. X
  412. X/*
  413. X * OPTIONALLY IGNORE NON-DIRECTORY (even if named as an input argument):
  414. X */
  415. X
  416. X    if (dflag && (type == FTW_F))
  417. X        return (0);
  418. X
  419. X/*
  420. X * HANDLE STARTING FILE:
  421. X */
  422. X
  423. X    if (startlen == 0)
  424. X    {
  425. X        startlen = strlen (filename);    /* set for later */
  426. X
  427. X        if (nextcol)        /* end previous line */
  428. X        NEWLINE;        /* sets nextcol == 0 */
  429. X
  430. X        printf ((type == FTW_D)   ? FMT_D    :
  431. X            (type == FTW_DNR) ? FMT_DNR    :
  432. X            (type == FTW_NS)  ? FMT_NS    : FMT_F1, filename);
  433. X
  434. X        return (0);
  435. X    }
  436. X
  437. X/*
  438. X * SET BASENAME FOR ALL OTHER TYPES:
  439. X */
  440. X
  441. X    basename = ((basename = strrchr (filename, '/')) == CPNULL) ?
  442. X           filename : (basename + 1);    /* past "/" if any */
  443. X
  444. X/*
  445. X * HANDLE NON-DIRECTORY SUB-FILE (print multiple per line):
  446. X */
  447. X
  448. X    if (type == FTW_F)
  449. X    {
  450. X        if (pflag)                /* else use preset value */
  451. X        filewidth = strlen (basename) + 1;
  452. X
  453. X        if (nextcol    && (nextcol + filewidth >= columns))    /* overflow */
  454. X        NEWLINE;            /* sets nextcol == 0 */
  455. X
  456. X        if (nextcol == 0)        /* start new line with indent */
  457. X        nextcol = PrintIndent (filename);
  458. X
  459. X        printf (FMT_F2, filewidth, basename);
  460. X        nextcol += filewidth;
  461. X        return (0);
  462. X    }
  463. X
  464. X/*
  465. X * HANDLE DIRECTORY OR OTHER SUB-FILE (print on line by itself):
  466. X */
  467. X
  468. X    if (nextcol)            /* end previous line */
  469. X        NEWLINE;            /* sets nextcol == 0 */
  470. X
  471. X    PrintIndent (filename);
  472. X
  473. X    printf ((type == FTW_D)   ? FMT_D   :
  474. X        (type == FTW_DNR) ? FMT_DNR : FMT_NS, basename);
  475. X
  476. X    return (0);
  477. X
  478. X} /* OneFile */
  479. X
  480. X
  481. X/************************************************************************
  482. X * P R I N T   I N D E N T
  483. X *
  484. X * Given a filename and globals startlen and indent, print the total
  485. X * indentation needed before the name, which is indent times the number of
  486. X * slashes past startlen (which should be >= 1).  Return the indent value.
  487. X */
  488. X
  489. XPROC int PrintIndent (filename)
  490. XREG    char    *filename;
  491. X{
  492. XREG    int    depth = 0;
  493. XREG    int    totind;
  494. X    int    retval;
  495. X
  496. X    filename += startlen;        /* start of sub-part */
  497. X
  498. X    while (*filename != CHNULL)
  499. X        if (*filename++ == '/')
  500. X        depth++;
  501. X
  502. X    retval = totind = indent * depth;
  503. X
  504. X    while (totind-- > 0)
  505. X        putchar (' ');
  506. X
  507. X    return (retval);
  508. X
  509. X} /* PrintIndent */
  510. X
  511. X
  512. X/************************************************************************
  513. X * U S A G E
  514. X *
  515. X * Print usage messages (char *usage[]) to stderr and exit nonzero.
  516. X * Each message is followed by a newline.
  517. X */
  518. X
  519. XPROC Usage()
  520. X{
  521. XREG    int    which = 0;        /* current line */
  522. X
  523. X    while (usage [which] != CPNULL)
  524. X    {
  525. X        fprintf (stderr, usage [which++], myname);
  526. X        putc ('\n', stderr);
  527. X    }
  528. X
  529. X    exit (1);
  530. X
  531. X} /* Usage */
  532. X
  533. X
  534. X/************************************************************************
  535. X * E R R O R
  536. X *
  537. X * Print an error message to stderr and exit nonzero.  Message is preceded
  538. X * by "<myname>: " using global char *myname, and followed by a newline.
  539. X */
  540. X
  541. X/* VARARGS */
  542. XPROC Error (message, arg1, arg2, arg3, arg4)
  543. X    char    *message;
  544. X    long    arg1, arg2, arg3, arg4;
  545. X{
  546. X    fprintf (stderr, "%s: ", myname);
  547. X    fprintf (stderr, message, arg1, arg2, arg3, arg4);
  548. X    putc ('\n', stderr);
  549. X
  550. X    exit (1);
  551. X
  552. X} /* Error */
  553. @//E*O*F hier.c//
  554. if test 8409 -ne "`wc -c <'hier.c'`"; then
  555.     echo shar: error transmitting "'hier.c'" '(should have been 8409 characters)'
  556. fi
  557. fi # end of overwriting check
  558. echo shar: extracting "'sftw.c'" '(14313 characters)'
  559. if test -f 'sftw.c' ; then 
  560.   echo shar: will not over-write existing file "'sftw.c'"
  561. else
  562. sed 's/^X//' >sftw.c <<'@//E*O*F sftw.c//'
  563. X/*
  564. X * Sorted file tree walk (library routine).
  565. X *
  566. X * Identical (in theory) to ftw(3), except:
  567. X *
  568. X * - Calls user's fn() with the files sorted alphabetically (per strcmp(3))
  569. X *   in two groups:  All non-directories first, followed by directories (with
  570. X *   the descendents of each directory after the directory).  Non-stat'able
  571. X *   files and non-readable directories are included in the second group.
  572. X *
  573. X * - Doesn't keep one file open for each level of recursion, so it doesn't
  574. X *   need a depth argument (which actually affects file opens/closes, NOT
  575. X *   maximum search depth).
  576. X *
  577. X * - Uses a lot more malloc space.
  578. X *
  579. X * - Supports an additional argument which tells it to include all files
  580. X *   and directories, including those whose names start with "." (except that
  581. X *   the given filename is always included, regardless of the flag, like
  582. X *   ls(1)).  The caller could implement this, but not very efficiently.
  583. X *
  584. X * Like ftw(), it ignores "." and ".." files, even with the all flag.
  585. X *
  586. X * For convenience, form of call is:
  587. X *
  588. X *    #include <ftw.h>
  589. X *
  590. X *    int sftw (path, fn, allfiles)
  591. X *        char    *path;
  592. X *        int    (*fn)();
  593. X *        int    allfiles;
  594. X *
  595. X * Form of fn() is:
  596. X *
  597. X *    int fn (name, statp, type)
  598. X *        char    *name;
  599. X *        struct    stat *statp;
  600. X *        int    type;
  601. X *
  602. X * See ftw(3) for more information.
  603. X *
  604. X * Compile with -DTEST to get a runnable test version that walks from "."
  605. X * and tells types, permissions, and filenames passed to fn().
  606. X */
  607. X
  608. X#include <sys/types.h>
  609. X#include <sys/stat.h>
  610. X#include <ftw.h>
  611. X#ifdef    BSD
  612. X#include <sys/dir.h>
  613. X#else
  614. X#include <ndir.h>
  615. X#endif    /* BSD */
  616. X
  617. Xstatic char *malloc(), *strcpy();
  618. Xstatic void free();
  619. X
  620. X
  621. X/*********************************************************************
  622. X * MISCELLANEOUS GLOBAL VALUES:
  623. X */
  624. X
  625. X#define    PROC                /* null; easy to find procs */
  626. X#define    FALSE    0
  627. X#define    TRUE    1
  628. X#define    CHNULL    ('\0')
  629. X#define    CPNULL    ((char *) NULL)
  630. X#define    REG    register
  631. X
  632. X/* built-up filename for passing to the user program; hope it's big enough */
  633. Xstatic    char filename [1000];
  634. X
  635. Xstatic    unsigned short euid, egid;    /* only get them once */
  636. X
  637. X
  638. X
  639. X/************************************************************************
  640. X * FILE DATA STRUCTURE:
  641. X *
  642. X * A contiguous array of pointers is used for sorting, built after knowing
  643. X * how many directory entries there are to sort.  Each entry points to a
  644. X * struct filedata which holds information for one directory entry.
  645. X */
  646. X
  647. Xtypedef    struct filedata *fdpt;
  648. Xtypedef    struct filedata **fdppt;
  649. X
  650. Xstruct filedata {
  651. X    char    *name;        /* in malloc'd space    */
  652. X    int    type;        /* see ftw.h        */
  653. X    struct    stat statbuf;    /* from stat(2)        */
  654. X};
  655. X
  656. X
  657. X/************************************************************************
  658. X * FILE AND STRING DATA BLOCKS:
  659. X *
  660. X * Since a directory may grow arbitrarily as it's read, there's no way to
  661. X * know in advance how big it is.  And it's necessary to return all malloc'd
  662. X * memory.  To make this possible, and to save malloc space and time, directory
  663. X * entry data and filenames are stored in buffers allocated a chunk a time.
  664. X */
  665. X
  666. X#define    DBENTRIES    20    /* file entries per datablock */
  667. X#define    STRINGENTRIES 1008    /* chars per string buffer    */
  668. X
  669. Xtypedef    struct datablock *dbpt;
  670. Xtypedef    struct datablock **dbppt;
  671. X
  672. Xstruct datablock {
  673. X    dbpt    next;                /* next block if any */
  674. X    struct    filedata fd [DBENTRIES];    /* the data itself   */
  675. X};
  676. X
  677. X#define    DBNULL  ((dbpt) NULL)
  678. X#define    DBSIZE  (sizeof (struct datablock))
  679. X
  680. X
  681. Xtypedef struct stringblock *sbpt;
  682. Xtypedef struct stringblock **sbppt;
  683. X
  684. Xstruct stringblock {
  685. X    sbpt    next;                /* next block if any */
  686. X    char    buf [STRINGENTRIES];        /* the data itself   */
  687. X};
  688. X
  689. X#define    SBNULL  ((sbpt) NULL)
  690. X#define    SBSIZE  (sizeof (struct stringblock))
  691. X
  692. X
  693. X/************************************************************************
  694. X * S F T W
  695. X *
  696. X * Handle the filename given by the user.  Since sftw() must stat() each
  697. X * file before sorting, the first (top level) file must be handled specially,
  698. X * not as part of re-entrant code.  (Think about it...)
  699. X */
  700. X
  701. XPROC int sftw (path, UserFunc, allfiles)
  702. X    char    *path;
  703. X    int    (*UserFunc)();
  704. X    int    allfiles;
  705. X{
  706. X    struct    stat statbuf;        /* from first file    */
  707. X    int    type;            /* of first file    */
  708. X    int    retval;            /* return by UserFunc()    */
  709. X    unsigned short geteuid(), getegid();
  710. X
  711. X    euid = geteuid();    /* initialize values */
  712. X    egid = getegid();
  713. X
  714. X/*
  715. X * HANDLE INITIAL FILE:
  716. X */
  717. X
  718. X    type = GetType (path, & statbuf);
  719. X
  720. X    if (retval = UserFunc (path, & statbuf, type))    /* it's unhappy */
  721. X        return (retval);
  722. X
  723. X    if (type != FTW_D)            /* we're done */
  724. X        return (0);
  725. X
  726. X/*
  727. X * WORK ON A READABLE DIRECTORY:
  728. X */
  729. X
  730. X    strcpy (filename, path);        /* now we can append to it */
  731. X    strcat (filename, "/");            /* prepare for additions   */
  732. X
  733. X    return (DoDirectory (UserFunc, allfiles));
  734. X
  735. X} /* sftw */
  736. X
  737. X
  738. X/************************************************************************
  739. X * D O   D I R E C T O R Y
  740. X *
  741. X * Given UserFunc(), all files flag, and global filename (directory path) where
  742. X * to start and on which to build complete pathnames, read the directory, sort
  743. X * filenames, and call UserFunc() for each file in the directory.  This routine
  744. X * calls itself to recurse, after each directory name is passed to UserFunc().
  745. X * Because it reads and saves a directory's contents in order to sort them, it
  746. X * does not keep any files open while recursing, just lots of memory.
  747. X *
  748. X * Free all memory from this level before returning, even in case of error.
  749. X * Return -1 in case of error, or the value from UserFunc() if non-zero.
  750. X */
  751. X
  752. XPROC static int DoDirectory (UserFunc, allfiles)
  753. X    int    (*UserFunc)();
  754. X    int    allfiles;        /* include ".*" files?      */
  755. X{
  756. X    dbpt    dbhead = DBNULL;    /* first datablock ptr      */
  757. X    sbpt    sbhead = SBNULL;    /* first stringblock ptr  */
  758. X
  759. X    fdppt    fdphead;        /* filedata list to sort  */
  760. XREG    fdppt    fdpcurr;        /* current list pointer      */
  761. XREG    fdpt    fdcurr;            /* current entry pointer  */
  762. XREG    int    files;            /* number in directory      */
  763. X
  764. X    int    retval;            /* copy of return value      */
  765. X
  766. X/* pointer into filename where to append basenames */
  767. XREG    char    *basename = filename + strlen (filename);
  768. X
  769. X    int    FDCmp();
  770. X    void    qsort();
  771. X
  772. X#define    RETURN(value) { FreeBlocks (dbhead, sbhead); return (value); }
  773. X
  774. X/*
  775. X * READ DIRECTORY:
  776. X */
  777. X
  778. X    if ((files = ReadDirectory (& dbhead, & sbhead, allfiles)) < 0)
  779. X        RETURN (-1);
  780. X
  781. X/*
  782. X * BUILD AND SORT POINTERS TO FILES:
  783. X *
  784. X * Get a big chunk of contiguous memory for the pointers, then set them up.
  785. X * Afterwards, filedata entries will be accessed via the pointers.
  786. X */
  787. X
  788. X    if ((fdphead = (fdppt) malloc (files * sizeof (fdpt))) == (fdppt) NULL)
  789. X        RETURN (-1);
  790. X
  791. X#undef    RETURN
  792. X#define    RETURN(value) { FreeBlocks (dbhead, sbhead); \
  793. X            free ((char *) fdphead); return (value); }
  794. X
  795. X    SetFDList (fdphead, fdphead + files, dbhead);
  796. X    qsort ((char *) fdphead, (unsigned) files, sizeof (fdpt), FDCmp);
  797. X
  798. X/*
  799. X * TRAVERSE FILES USING SORTED POINTERS:
  800. X *
  801. X * Append each file's basename to the current path in global filename,
  802. X * overlaying whatever basename was there before, and pass it to UserFunc().
  803. X */
  804. X
  805. X    fdpcurr = fdphead;
  806. X
  807. X    while (files-- > 0)
  808. X    {
  809. X        strcpy (basename, (fdcurr = (*fdpcurr++)) -> name);
  810. X
  811. X        if (retval = UserFunc (filename, & (fdcurr -> statbuf),
  812. X                   fdcurr -> type))  /* it's unhappy */
  813. X        {
  814. X        RETURN (retval);
  815. X        }
  816. X
  817. X/*
  818. X * RECURSE FOR A DIRECTORY:
  819. X */
  820. X
  821. X        if ((fdcurr -> type) == FTW_D)
  822. X        {
  823. X        strcat (basename, "/");        /* for next level */
  824. X
  825. X        if (retval = DoDirectory (UserFunc, allfiles))
  826. X            RETURN (retval);
  827. X        }
  828. X    }
  829. X
  830. X    RETURN (0);
  831. X
  832. X} /* DoDirectory */
  833. X
  834. X
  835. X/************************************************************************
  836. X * R E A D   D I R E C T O R Y
  837. X *
  838. X * Given pointers to datablock and stringblock chain head pointers, all files
  839. X * flag, and global filename (name of a readable directory) on which to build
  840. X * complete pathnames, open, read, and close a directory, saving name, stat,
  841. X * and type information on each file in a chain of datablocks and stringblocks,
  842. X * and setting the chain head pointers.  Return the number of filenames read
  843. X * and saved (>= 0), or -1 in case of error, but always close the directory.
  844. X */
  845. X
  846. XPROC static int ReadDirectory (dbheadp, sbheadp, allfiles)
  847. X    dbppt    dbheadp;        /* start of chain    */
  848. X    sbppt    sbheadp;        /* start of chain    */
  849. X    int    allfiles;        /* include ".*" files?    */
  850. X{
  851. XREG    DIR    *dirp;            /* for reading directory  */
  852. XREG    struct    direct *entp;        /* directory entry      */
  853. XREG    char    *name;            /* fast copy of filename  */
  854. X
  855. XREG    dbpt    dbcurr;            /* current datablock ptr  */
  856. X    dbppt    dbnextp = dbheadp;    /* next datablock ptr ptr */
  857. XREG    int    dbentry = DBENTRIES;    /* current entry in block */
  858. X
  859. XREG    fdpt    fdcurr;            /* current filedata entry */
  860. X
  861. XREG    sbpt    sbcurr;            /* current stringblock ptr  */
  862. X    sbppt    sbnextp = sbheadp;    /* next stringblock ptr ptr */
  863. XREG    char    *end   = "";        /* last + 1 of block        */
  864. XREG    char    *start = end;        /* next free place        */
  865. X
  866. X/* pointer into filename where to append basenames */
  867. XREG    char    *basename = filename + strlen (filename);
  868. XREG    int    files      = 0;        /* files read and saved      */
  869. X
  870. X#undef    RETURN
  871. X#define    RETURN(value) { closedir (dirp); return (value); }
  872. X
  873. X/*
  874. X * OPEN AND READ DIRECTORY:
  875. X */
  876. X
  877. X    if ((dirp = opendir (filename)) == (DIR *) NULL)
  878. X        return (-1);        /* hope errno is set */
  879. X
  880. X    /* now be sure to use the RETURN() macro */
  881. X
  882. X    while ((entp = readdir (dirp)) != (struct direct *) NULL)
  883. X    {
  884. X
  885. X/*
  886. X * OPTIONALLY SKIP ".*" FILES:
  887. X *
  888. X * Always skip "." and ".." files, like ftw().
  889. X */
  890. X
  891. X        if ((* (name = entp -> d_name) == '.')    /* fast check */
  892. X         && ((! allfiles)
  893. X          ||  (name [1] == CHNULL)
  894. X          || ((name [1] == '.') && (name [2] == CHNULL))))
  895. X        {
  896. X        continue;
  897. X        }
  898. X
  899. X        files++;
  900. X
  901. X/*
  902. X * GET A NEW DATABLOCK; APPEND TO CHAIN:
  903. X */
  904. X
  905. X        if (dbentry >= DBENTRIES)        /* block is full */
  906. X        {
  907. X        if ((dbcurr = *dbnextp = (dbpt) malloc (DBSIZE)) == DBNULL)
  908. X            RETURN (-1);
  909. X
  910. X        * (dbnextp = & (dbcurr -> next)) = DBNULL;
  911. X        dbentry = 0;
  912. X        }
  913. X
  914. X/*
  915. X * GET A NEW STRINGBLOCK; APPEND TO CHAIN:
  916. X *
  917. X * Yes, we may abandon some unused space in the previous block...  Hope that
  918. X * STRINGENTRIES is much larger than the average directory entry name size.
  919. X */
  920. X
  921. X        if ((entp -> d_namlen) + 1 > (end - start))
  922. X        {
  923. X        if ((sbcurr = *sbnextp = (sbpt) malloc (SBSIZE)) == SBNULL)
  924. X            RETURN (-1);
  925. X
  926. X        * (sbnextp = & (sbcurr -> next)) = SBNULL;
  927. X        end = (start = (sbcurr -> buf)) + STRINGENTRIES;
  928. X        }
  929. X
  930. X/*
  931. X * SAVE INFORMATION ON ONE FILE:
  932. X *
  933. X * Append each file's basename to the current path in global filename,
  934. X * overlaying whatever basename was there before, and pass it to GetType().
  935. X */
  936. X
  937. X        fdcurr = (dbcurr -> fd) + (dbentry++);    /* quick pointer */
  938. X
  939. X        strcpy (((fdcurr -> name) = start), name);
  940. X        start += (entp -> d_namlen) + 1;
  941. X
  942. X        strcpy (basename, name);
  943. X        (fdcurr -> type) = GetType (filename, & (fdcurr -> statbuf));
  944. X
  945. X    } /* while */
  946. X
  947. X    RETURN (files);
  948. X
  949. X} /* ReadDirectory */
  950. X
  951. X
  952. X/************************************************************************
  953. X * G E T   T Y P E
  954. X *
  955. X * Given a filename and a pointer to a stat structure, stat() the file into the
  956. X * structure and return an appropriate ftw() type.  Since directories are not
  957. X * opened for reading until much later, after sorting, determine readability
  958. X * now using euid, egid, and st_mode.  Can't use access(2) because it checks
  959. X * real ids, not effective (sigh).
  960. X */
  961. X
  962. XPROC static int GetType (name, statp)
  963. X    char    *name;
  964. X    struct    stat *statp;
  965. X{
  966. X#define    UREAD (S_IREAD)         /* read permission bits for user, group, other */
  967. X#define    GREAD (S_IREAD >> 3)
  968. X#define    OREAD (S_IREAD >> 6)
  969. X
  970. X    if (stat (name, statp) < 0)
  971. X        return (FTW_NS);            /* not stat'able */
  972. X
  973. X    if (((statp -> st_mode) & S_IFMT) != S_IFDIR)
  974. X        return (FTW_F);            /* not directory */
  975. X
  976. X    /* pick appropriate permission bit, then see if it's set */
  977. X
  978. X    return (
  979. X        ( ((statp -> st_uid) == euid) ? ((statp -> st_mode) & UREAD) :
  980. X          ((statp -> st_gid) == egid) ? ((statp -> st_mode) & GREAD) :
  981. X                        ((statp -> st_mode) & OREAD) ) ?
  982. X        FTW_D : FTW_DNR);
  983. X
  984. X} /* GetType */
  985. X
  986. X
  987. X/************************************************************************
  988. X * S E T   F D   L I S T
  989. X *
  990. X * Given pointers to the current (head) and end + 1 of an array of
  991. X * uninitialized struct filedata pointers, and a current (head) struct
  992. X * datablock pointer, fill in the pointers in the array to point to the
  993. X * filedata entries in the datablock chain.
  994. X */
  995. X
  996. XPROC static SetFDList (fdpcurr, fdpend, dbcurr)
  997. XREG    fdppt    fdpcurr;    /* start at head */
  998. XREG    fdppt    fdpend;        /* last + 1     */
  999. XREG    dbpt    dbcurr;        /* start at head */
  1000. X{
  1001. XREG    int    dbentry;    /* current index */
  1002. X
  1003. X    while (TRUE)                /* until return */
  1004. X    {
  1005. X        for (dbentry = 0; dbentry < DBENTRIES; dbentry++)    /* one block */
  1006. X        {
  1007. X        if (fdpcurr >= fdpend)        /* no more */
  1008. X            return;
  1009. X
  1010. X        *fdpcurr++ = (dbcurr -> fd) + dbentry;
  1011. X        }
  1012. X
  1013. X        dbcurr = dbcurr -> next;
  1014. X    }
  1015. X
  1016. X    /* never get here */
  1017. X
  1018. X} /* SetFDList */
  1019. X
  1020. X
  1021. X/************************************************************************
  1022. X * F D   C M P
  1023. X *
  1024. X * Given two pointers to pointers to filedata entries, compare the entries
  1025. X * and return -1, 0, or 1 for how they relate.  "Normal" files (FTW_F)
  1026. X * are always lower than other types, then names are compared with strcmp().
  1027. X */
  1028. X
  1029. XPROC static int FDCmp (fdpp1, fdpp2)
  1030. X    fdppt    fdpp1, fdpp2;
  1031. X{
  1032. XREG    int    type1 = (*fdpp1) -> type;
  1033. XREG    int    type2 = (*fdpp2) -> type;
  1034. X
  1035. X    return (((type1 == FTW_F) && (type2 != FTW_F)) ? -1 :
  1036. X        ((type1 != FTW_F) && (type2 == FTW_F)) ?  1 :
  1037. X        strcmp ((*fdpp1) -> name, (*fdpp2) -> name));
  1038. X
  1039. X} /* FDCmp */
  1040. X
  1041. X
  1042. X/************************************************************************
  1043. X * F R E E   B L O C K S
  1044. X *
  1045. X * Given pointers to heads of datablock and stringblock chains, free the
  1046. X * malloc'd memory in the chains.
  1047. X */
  1048. X
  1049. XPROC static FreeBlocks (dbhead, sbhead)
  1050. XREG    dbpt    dbhead;
  1051. XREG    sbpt    sbhead;
  1052. X{
  1053. XREG    dbpt    dbtemp;
  1054. XREG    sbpt    sbtemp;
  1055. X
  1056. X    while (dbhead != DBNULL)
  1057. X    {
  1058. X        dbtemp = dbhead -> next;
  1059. X        free ((char *) dbhead);
  1060. X        dbhead = dbtemp;
  1061. X    }
  1062. X
  1063. X    while (sbhead != SBNULL)
  1064. X    {
  1065. X        sbtemp = sbhead -> next;
  1066. X        free ((char *) sbhead);
  1067. X        sbhead = sbtemp;
  1068. X    }
  1069. X
  1070. X} /* FreeBlocks */
  1071. X
  1072. X
  1073. X#ifdef TEST
  1074. X
  1075. X/************************************************************************
  1076. X * TEST HARNESS:
  1077. X */
  1078. X
  1079. XPROC int fn (name, statp, type)
  1080. X    char    *name;
  1081. X    struct    stat *statp;
  1082. X    int    type;
  1083. X{
  1084. X    printf ("%-3s %06o \"%s\"\n",
  1085. X        (type == FTW_F)   ? "F"   : (type == FTW_D)  ? "D"  :
  1086. X        (type == FTW_DNR) ? "DNR" : (type == FTW_NS) ? "NS" : "?",
  1087. X        statp -> st_mode, name);
  1088. X
  1089. X    return (0);
  1090. X}
  1091. X
  1092. XPROC main()
  1093. X{
  1094. X    printf ("sftw returns %d\n", sftw (".", fn));
  1095. X}
  1096. X
  1097. X#endif TEST
  1098. @//E*O*F sftw.c//
  1099. if test 14313 -ne "`wc -c <'sftw.c'`"; then
  1100.     echo shar: error transmitting "'sftw.c'" '(should have been 14313 characters)'
  1101. fi
  1102. fi # end of overwriting check
  1103. echo shar: "End of shell archive."
  1104. exit 0
  1105.